#!/usr/bin/python
########################################################################
# c4drender - C4D server-side rendering script.  Reads a specified path
#	and renders all unrendered C4D files
#
# Created 2006.06.11 by Rick Barrett
#
# Copyright (c) 2006 by Pixel Corps.  This program may be used under
# the terms of the X11 license, a copy of which should have accompanied
# this program, in the LICENSE file.
########################################################################

from types import *
from path import path
from fnmatch import fnmatch

import sys
import os
import getopt
import string
import datetime
import libxml2

## Global variables
gC4DPath = None				# -c argument
gDefaultPath = None			# -d argument
gNodeList = []				# -n argument
gRecurse = False 			# -r argument
gServer = None				# -s argument
gMode = 0					# -M argument
gInputPath = []				# files args

def usage():
	"""Print the program's usage message"""

	print """
usage: c4drender [-d root-path] [-r] files...

    -c  CINEMA 4D application path
    -d  Root path to search for scene files
    -r  Recurse subdirectories
    -n  Distribution Node (call once per node)
    -s  Server address
    -M  Mode (0=Render, 1=Distribute, 2=Collect)

    When specifying files to in(ex)clude, you may use * and ? wildcards
    
    You must specify -c and either -d or files.  If you specify both -d
	and files, files will be searched relative to path.
"""

def ParseCommandLine():
    """
    Parse the options and arguments given on the command line
    @rtype: boolean
    @returns: TRUE if command line arguments were parsed successfully
    """

    global gDefaultPath, gInputPath, gRecurse
    global gC4DPath, gMode, gNodeList, gServer
    
    ## Parse options on command line
    try:
        opts, args = getopt.getopt(sys.argv[1:], "c:d:n:rs:M:")

	# Check parsed options for sanity.  If all is okay, we should
	# popupate most of the global variables.

        for opt, val in opts:
            
            if opt == '-d':
                if os.path.exists(val):
                    gDefaultPath = val
                else:
                    print 'Default path does not exist'
                    usage()
                    return False

            elif opt == '-c':
                if os.path.exists(val):
                    gC4DPath = val
                else:
                    print 'C4D Path does not exist'
                    usage()
                    return False
                        
            elif opt == '-n':
                if not ":" in val:
                    val = val + ":"
                gNodeList.append(val)
                    
            elif opt == '-r':
                gRecurse = True
                
            elif opt == '-s':
                gServer = val
             
            elif opt == '-M':
                if val == "0" or val == "1" or val == "2":
                    gMode = int(val)
                else:
                    print 'Invalid Mode (0=Render, 1=Distribute, 2=Collect)'
                    usage()
                    return False
                    
        # Save non-option command line arguments as the path(s) to search
        if args:
            gInputPath = args

    except getopt.GetoptError, err:
        print 'Option parsing error: ' + str(err)
        usage()
        return False

    ## Given parsed options, infer defaults, etc.
    if not gInputPath:
        print 'You must specify an input path'
        usage()
        return False
    elif not gC4DPath and gMode==0:
        print 'You must specify a C4D path'
        usage()
        return False
    elif not gNodeList and gMode==1:
        print 'You must specify at least one node in Distribute mode'
    elif not gServer and gMode==2:
        print 'You must specify a server address in Collect mode'
 
    return True

def spawn(program, args):
    """
    Spawns a process utilizing the most preferred supported method of three
    options.
    
    @type program: path
    @param program: path to executable or application to be spawned
    
    @type args: tuple
    @param args: command line arguments for spawned process
    
    @rtype: boolean
    @return: TRUE if process was spawned successfully
    """
    
    if os.name in ("nt", "dos"):
        exefile = ".exe"
    else:
        exefile = ""
    try:
        # check if the os module provides a shortcut
        return os.spawnvp(os.P_WAIT, program, (program,) + args)
    except AttributeError:
        pass
    try:
        if os.path.split(program)[0]:
            os.chdir(os.path.split(program)[0])
        program = os.path.split(program)[1]
        return os.spawnv(os.P_WAIT, program, (program,) + args)
    except AttributeError:
        # assume it's unix
        pid = os.fork()
        if not pid:
            os.execvp(program, (program,) + args)
            return os.wait()[0]

def scp(local, remote):
    """
    Utilizes SCP to transfer files to a remote server.
    
    @param local: local path
    @param remote: remote path
    
    @rtype: boolean
    @return: TRUE if transfer is successful
    """
    return os.system("scp " + local + " " + remote)

def C4DPath(path):
    """
    If OS is Macintosh, returns HFS path based on posix path input, 
    otherwise returns the OS native path. This function is necessary because
    C4D R9 and lesser utilize HFS style pathnames.
    
    Utilizes C{Carbon.File} library.
    
    @type path: path
    @param path: OS native pathname
    
    @rtype: string
    @return: HFS compliant path
    """
    
    if os.name in ("mac", "posix"):
        import Carbon.File
        vol = Carbon.File.FSRef(path).FSNewAliasMinimal().GetAliasInfo(-1)
        return '\"' + vol + path.replace("/",":") + '\"'
    else:
        return '\"' + path + '\"'

def ProcessFiles():
    """
    Find C4D files and process based on L{gMode} variable (-M)
    
    Global L{gMode}:
        - 0 Render - use C4D command-line to render .c4d files found within
            L{gInputPath} (-d or files argument)
        - 1 Distribute - uses scp to send C4D files found within L{gInputPath} 
            to nodes specified in L{gNodeList} array (-n).
        - 2 Collect - uses L{scp} to transfer Quicktime .mov files to 
            server specified in L{gServer} (-s). Usually called after files 
            are compiled by WickDD slategen
    """

    node = 0

    #print "checking for files in " + gInputPath
    for i in gInputPath:
    
        oFile = path(i)
        
        if gRecurse == True:
            fileList = oFile.walkfiles()
        else:
            fileList = oFile.listdir()

        for item in fileList:
            # Check for C4D Files
            if os.path.splitext(item)[1].upper() == ".C4D":
                nameBase = os.path.splitext(item)[0]
                if not os.path.exists(nameBase + '.mov'):
                    if gMode==0:
                        c4dpath = C4DPath(item.abspath())
                        args = (' -nogui',
                                ' -render ' + c4dpath,
                                ' -oformat TIFF'
                                ' -oimage ' + \
                                os.path.splitext(c4dpath)[0] + '.TIF')
                        print datetime.datetime.utcnow(), "Rendering", \
                              os.path.basename(item), \
                              "result:", spawn(gC4DPath, args)
                    elif gMode==1:
                        if os.path.exists(nameBase + '.xml'):
                            xml = libxml2.parseFile(nameBase + '.xml')
                            xpathContext = xml.xpathNewContext()
                            pFiles = xpathContext.xpathEval("//file/remotepath")
                            for pF in pFiles:
                                if os.path.exists(pF.content.strip()):
                                    print datetime.datetime.utcnow(), "Transferring", \
                                          os.path.basename(pF.content.strip()), \
                                          "to", gNodeList[node] + "/" + \
                                          os.path.split(nameBase)[1], \
                                          "result:", \
                                          scp(pF.content.strip(),
                                              gNodeList[node] + "/" + \
                                              os.path.split(nameBase)[1])
                                    if node== len(gNodeList) - 1:
                                        node = 0
                                    else:
                                        node = node + 1
                        else:
                            print datetime.datetime.utcnow(), "Transferring", \
                                  os.path.basename(item.abspath()), \
                                  "to", gNodeList[node] + "/" + \
                                  os.path.split(nameBase)[1], \
                                  "result:", \
                                  scp(item.abspath(),
                                      gNodeList[node] + "/" + \
                                      os.path.split(nameBase)[1])
                else:
                    if gMode==2:
                        print datetime.datetime.utcnow(), "Transferring", \
                              os.path.basename(nameBase + '.mov'), \
                              "to", gServer, \
                              "result:", \
                              scp(nameBase + '.mov', gServer)

def main():
    """
    Main program execution function. Parses commandline and utilizes C4D
    command-line renderer to process files
    @rtype: boolean
    @return: TRUE if program executes successfully and RSS file is created
    """

    if ParseCommandLine() and ProcessFiles():
        print 'Files rendered'
    else:
        sys.exit(1)
        
if __name__ == "__main__": main()
